package com.ccrfid.rmc6.service.asset;

import com.ccrfid.rmc6.appconfig.exception.pojo.AppException;
import com.ccrfid.rmc6.appconfig.exception.pojo.CustomErrors;
import com.ccrfid.rmc6.entity.asset.AssetEntity;
import com.ccrfid.rmc6.entity.asset.AssetMoveEntity;
import com.ccrfid.rmc6.entity.asset.AssetView;
import com.ccrfid.rmc6.entity.device.CardEntity;
import com.ccrfid.rmc6.lbservice3.pojo.msg.MsgLocate;
import com.ccrfid.rmc6.pojo.dto.AssetMoveDto;
import com.ccrfid.rmc6.pojo.dto.AssetMoveInLocationDto;
import com.ccrfid.rmc6.pojo.dto.LocatorAssetCount;
import com.ccrfid.rmc6.repository.asset.AssetMoveRepository;
import com.ccrfid.rmc6.repository.asset.AssetRepository;
import com.ccrfid.rmc6.repository.asset.AssetViewRepository;
import com.ccrfid.rmc6.repository.device.CardRepository;
import com.ccrfid.rmc6.repository.search.SearchCriteria;
import com.ccrfid.rmc6.util.DateUtil;
import org.hibernate.query.NativeQuery;
import org.hibernate.spatial.JTSGeometryType;
import org.hibernate.spatial.dialect.mysql.MySQLGeometryTypeDescriptor;
import org.hibernate.transform.Transformers;
import org.hibernate.type.IntegerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.core.Response;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
public class AssetServiceImpl implements AssetService {
    private static final Logger logger = LoggerFactory.getLogger(AssetServiceImpl.class);
    @PersistenceContext
    private EntityManager entityManager;
    @Autowired
    private AssetRepository assetRepository;
    @Autowired
    private CardRepository cardRepository;
    @Autowired
    private AssetMoveRepository assetMoveRepository;
    @Autowired
    private AssetViewRepository assetViewRepository;

    @Override
    public AssetEntity getAssetById(Long assetId) {
        return assetRepository.getById(assetId);
    }

    @Override
    public AssetEntity getAssetByCardNumber(Long cardNumber) {
        return assetRepository.getByCardNumber(cardNumber);
    }

    @Override
    public AssetMoveEntity recordAssetMove(Long assetId, MsgLocate msgLocate) {
        AssetMoveEntity assetMoveEntity = new AssetMoveEntity();
        assetMoveEntity.setAssetId(assetId);
        assetMoveEntity.setCardNumber(msgLocate.getCardNumber());
        assetMoveEntity.setFrontLocatorAddress(msgLocate.getFrontLocator());
        assetMoveEntity.setFrontX(msgLocate.getFrontPos() != null ? msgLocate.getFrontPos().getX() : null);
        assetMoveEntity.setFrontY(msgLocate.getFrontPos() != null ? msgLocate.getFrontPos().getY() : null);
        assetMoveEntity.setFrontFloorNumber(msgLocate.getFrontFloorNumber());
        assetMoveEntity.setCurrentLocatorAddress(msgLocate.getCurrentLocator());
        assetMoveEntity.setCurrentX(msgLocate.getCurrentPos() != null ? msgLocate.getCurrentPos().getX() : null);
        assetMoveEntity.setCurrentY(msgLocate.getCurrentPos() != null ? msgLocate.getCurrentPos().getY() : null);
        assetMoveEntity.setCurrentFloorNumber(msgLocate.getCurrentFloorNumber());
        assetMoveEntity.setRecordTime(new Timestamp(msgLocate.getTime()));
        return assetMoveRepository.save(assetMoveEntity);
    }

    @Override
    public Page<AssetEntity> getAssetsInfoPage(SearchCriteria criteria, Pageable pageable) {
        return assetRepository.search(criteria, pageable);
    }

    @Override
    public AssetEntity createAsset(AssetEntity entity) throws AppException {
        if (entity.getAssetName() == null) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.asset_name_is_null.getCode(), CustomErrors.asset_name_is_null.getReason(), "");
        }
        if (entity.getEnabled() == null) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.enabled_is_null.getCode(), CustomErrors.enabled_is_null.getReason(), "");
        }
        if (entity.getAssetSn() != null) {
            if (assetRepository.existsByAssetSn(entity.getAssetSn())) {
                throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                        CustomErrors.asset_sn_conflict.getCode(), CustomErrors.asset_sn_conflict.getReason(), "");
            }
        }
        if (entity.getCardNumber() != null) {
            if (assetRepository.existsByCardNumber(entity.getCardNumber())) {
                throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                        CustomErrors.card_number_conflict.getCode(), CustomErrors.card_number_conflict.getReason(), "");
            }
        }
        return assetRepository.save(entity);
    }

    @Override
    public AssetEntity updateAsset(Long assetId, AssetEntity entity) throws AppException {
        if (!assetRepository.existsById(assetId)) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
        if (!assetId.equals(entity.getId())) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.different_args.getCode(), CustomErrors.different_args.getReason(), "");
        }
        if (entity.getAssetName() == null) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.asset_name_is_null.getCode(), CustomErrors.asset_name_is_null.getReason(), "");
        }
        if (entity.getEnabled() == null) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.enabled_is_null.getCode(), CustomErrors.enabled_is_null.getReason(), "");
        }
        if (entity.getAssetSn() != null) {
            if (assetRepository.existsByAssetSnAndIdNot(entity.getAssetSn(), assetId)) {
                throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                        CustomErrors.asset_sn_conflict.getCode(), CustomErrors.asset_sn_conflict.getReason(), "");
            }
        }
        if (entity.getCardNumber() != null) {
            if (assetRepository.existsByCardNumberAndIdNot(entity.getCardNumber(), assetId)) {
                throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                        CustomErrors.card_number_conflict.getCode(), CustomErrors.card_number_conflict.getReason(), "");
            }
        }
        return assetRepository.save(entity);
    }

    @Override
    public Boolean deleteAssets(List<Long> idList) throws AppException {
        if (idList == null) {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.param_is_null.getCode(), CustomErrors.param_is_null.getReason(), "");
        }
        for (Long assetId : idList) {
            if (assetRepository.existsById(assetId)) {
                assetRepository.deleteById(assetId);
            }
        }
        return true;
    }

    @Override
    public Boolean bindAssetCard(Long assetId, Long cardNumber) throws AppException {
        if (!assetRepository.existsById(assetId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
        if (!cardRepository.existsByCardNumber(cardNumber)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.card_not_found.getCode(), CustomErrors.card_not_found.getReason(), "");
        }
        if (assetRepository.existsByCardNumber(cardNumber)) {
            throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                    CustomErrors.card_already_binded.getCode(), CustomErrors.card_already_binded.getReason(), "");
        }
        AssetEntity assetEntity = assetRepository.getById(assetId);
        if (assetEntity.getCardNumber() != null) {
            throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                    CustomErrors.person_already_binded.getCode(), CustomErrors.person_already_binded.getReason(), "");
        }
        CardEntity cardEntity = cardRepository.getByCardNumber(cardNumber);
        if(cardEntity.getHexNumber() != null){
            assetEntity.setHexNumber(cardEntity.getHexNumber());
        }
        assetEntity.setCardNumber(cardNumber);
        assetRepository.save(assetEntity);
        return true;
    }

    @Override
    public Boolean unBindAssetCard(Long assetId, Long cardNumber) throws AppException {
        if (!assetRepository.existsById(assetId)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
        if (!cardRepository.existsByCardNumber(cardNumber)) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.card_not_found.getCode(), CustomErrors.card_not_found.getReason(), "");
        }
        AssetEntity assetEntity = assetRepository.getById(assetId);
        if (assetEntity.getCardNumber() == null || !assetEntity.getCardNumber().equals(cardNumber)) {
            throw new AppException(Response.Status.CONFLICT.getStatusCode(),
                    CustomErrors.different_asset_card.getCode(), CustomErrors.different_asset_card.getReason(), "");
        }
        assetEntity.setCardNumber(null);
        assetEntity.setHexNumber(null);
        assetRepository.save(assetEntity);
        return true;
    }

    @Override
    public List<AssetView> getAssetInfoListByLocatorAddressIsNotNull() {
        return assetViewRepository.getAllByLocatorAddressIsNotNull();
    }

    @Override
    public List<AssetView> getAssetInfoListByLocatorAddress(Integer locatorAddress) {
        return assetViewRepository.getAllByLocatorAddress(locatorAddress);
    }

    @Override
    public List<LocatorAssetCount> getAssetDistribution(Integer floorNumber) {
        String sql = "SELECT locator_address as locatorAddress, pos, floor_number as floorNumber, count(*) as count " +
                " FROM v_asset " +
                " where is_enabled = 1 and pos is not null and floor_number = :fn " +
                " group by pos, locator_address";
        List<LocatorAssetCount> list = entityManager.createNativeQuery(sql)
                .setParameter("fn", floorNumber)
                .unwrap(NativeQuery.class)
                .addScalar("locatorAddress", IntegerType.INSTANCE)
                .addScalar("pos", new JTSGeometryType(MySQLGeometryTypeDescriptor.INSTANCE))
                .addScalar("floorNumber", IntegerType.INSTANCE)
                .addScalar("count", IntegerType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(LocatorAssetCount.class))
                .list();
        return list;
    }

    @Override
    public List<AssetMoveInLocationDto> getAssetMoveLocationList(Long assetId, Timestamp startTime, Timestamp endTime) {
        List<AssetMoveDto> moveList = getAssetMoveList(assetId, DateUtil.toDateTimeStr(startTime), DateUtil.toDateTimeStr(endTime));
        if (moveList.size() > 0) {
            List<AssetMoveInLocationDto> list = new ArrayList<>();
            int pos = 0;
            for (int i = 0; i < moveList.size(); i++) {
                AssetMoveDto moveDto = moveList.get(i);
                if (moveDto.getLocationId() != null) {
                    list.add(createMoveInLocationDto(moveDto));
                    pos = i;
                    break;
                }
            }
            for (int i = pos + 1; i < moveList.size(); i++) {
                AssetMoveInLocationDto dto = list.get(list.size() - 1);
                AssetMoveDto frontDto = moveList.get(i - 1);
                AssetMoveDto currentDto = moveList.get(i);
                if (frontDto.getLocationId() != null) {
                    dto.setLeaveTime(currentDto.getRecordTime());
                }
                if ((currentDto.getLocationId() != null && !currentDto.getLocationId().equals(dto.getLocationId()))
                        || (currentDto.getLocationId() != null && frontDto.getLocationId() == null)) {
                    list.add(createMoveInLocationDto(currentDto));
                }
            }
            return list;
        } else {
            return new ArrayList<>();
        }
    }

    private List<AssetMoveDto> getAssetMoveList(Long assetId, String startTime, String endTime) {
        String sql = "select m.id, m.asset_id, m.card_number, m.front_locator_address, m.current_locator_address, " +
                " m.record_time, p.id as location_id, p.location_name, p.location_type, p.map_level, p.location_centroid," +
                " p.floor_number, p.bind_org_id " +
                " from asset_move m " +
                " left join (select * from poi_location where map_level = 3) p " +
                " on st_contains(p.location_polygon, point(m.current_x, m.current_y)) and p.floor_number = m.current_floor_number " +
                " where m.asset_id = :assetId " +
                " and (m.record_time BETWEEN '" + startTime + "' and '" + endTime + "') " +
                " order by m.id";
        List<AssetMoveDto> moveList = entityManager.createNativeQuery(sql, AssetMoveDto.class)
                .setParameter("assetId", assetId)
                .getResultList();
        return moveList;
    }

    private AssetMoveInLocationDto createMoveInLocationDto(AssetMoveDto moveDto) {
        AssetMoveInLocationDto moveInLocationDto = new AssetMoveInLocationDto();
        moveInLocationDto.setAssetId(moveDto.getAssetId());
        moveInLocationDto.setCardNumber(moveDto.getCardNumber());
        moveInLocationDto.setLocationId(moveDto.getLocationId());
        moveInLocationDto.setLocationName(moveDto.getLocationName());
        moveInLocationDto.setLocationType(moveDto.getLocationType());
        moveInLocationDto.setLocationCentroid(moveDto.getLocationCentroid());
        moveInLocationDto.setFloorNumber(moveDto.getFloorNumber());
        moveInLocationDto.setEnterTime(moveDto.getRecordTime());
        return moveInLocationDto;
    }
}
